//===========================================================================//
// Copyright (C) Microsoft Corporation. All rights reserved.                 //
//===========================================================================//

#include "StuffHeaders.hpp"

OBB
	OBB::Identity(LinearMatrix4D::Identity, Vector3D::Identity, 0.0f);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	OBB::TestInstance() const
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
#if !defined(Spew)
	void
		Spew(
			const char* group,
			const OBB &box
		)
	{
		Check_Object(&box);
		SPEW((group, ""));
		SPEW((group, "  Transform = +"));
		Spew(group, box.localToParent);
		SPEW((group, ""));
		SPEW((
			group,
			"  Extents = <%4f,%4f,%4f>, Radius = %4f+",
			box.axisExtents.x,
			box.axisExtents.y,
			box.axisExtents.z,
			box.sphereRadius
		));
	}
#endif

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
OBB&
	OBB::Multiply(
		const OBB &obb,
		const LinearMatrix4D &matrix
	)
{
	Check_Pointer(this);
	Check_Object(&obb);
	Check_Object(&matrix);

	localToParent.Multiply(obb.localToParent, matrix);
	axisExtents = obb.axisExtents;
	sphereRadius = obb.sphereRadius;
	return *this;
}

#if 0	// moved it into hpp-file
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
OBB&
	OBB::MultiplySphereOnly(
		const OBB &obb,
		const LinearMatrix4D &matrix
	)
{
	Check_Pointer(this);
	Check_Object(&obb);
	Check_Object(&matrix);

#if USE_ASSEMBLER_CODE

	Scalar *f = localToParent.entries;

	_asm {
		mov         edx, matrix
		push        esi
		mov         esi, obb.localToParent
		
		mov         eax, f

		fld         dword ptr [edx+4]		//	m[1][0]
		fmul        dword ptr [esi+01ch]	//	obb.localToParent(3,1)
		
		fld         dword ptr [edx+8]		//	m[2][0]
		fmul        dword ptr [esi+02Ch]	//	obb.localToParent(3,2)

		fxch		st(1)
		fadd        dword ptr [edx+0Ch]		//	m[3][0]
		
		fld         dword ptr [edx]			//	m[0][0]
		fmul        dword ptr [esi+0Ch]		//	obb.localToParent(3,0)

		fxch		st(2)
		faddp       st(1),st

		fld         dword ptr [edx+14h]		//	m[1][1]
		fmul        dword ptr [esi+01ch]	//	obb.localToParent(3,1)

		fxch		st(2)
		faddp       st(1),st

		fld         dword ptr [edx+18h]		//	m[2][1]
		fmul        dword ptr [esi+02ch]	//	obb.localToParent(3,2)
		
		fxch		st(1)
		fstp        dword ptr [eax+0ch]		//	localToParent(3,0)

		fadd        dword ptr [edx+1Ch]		//	m[3][1]

		fld         dword ptr [edx+10h]		//	m[0][1]
		fmul        dword ptr [esi+0ch]		//	obb.localToParent(3,0)

		fxch		st(2)
		faddp       st(1),st

		fld         dword ptr [edx+24h]		//	m[1][2]
		fmul        dword ptr [esi+01ch]	//	obb.localToParent(3,1)

		fxch		st(2)
		faddp       st(1),st

		fld         dword ptr [edx+28h]		//	m[2][2]
		fmul        dword ptr [esi+02ch]	//	obb.localToParent(3,2)
		
		fxch		st(1)
		fstp        dword ptr [eax+01ch]	//	localToParent(3,1)

		fadd        dword ptr [edx+2Ch]		//	m[3][2]
		
		fld         dword ptr [edx+20h]		//	m[0][2]
		fmul        dword ptr [esi+0ch]		//	obb.localToParent(3,0)

		fxch		st(2)
		faddp       st(1),st

		pop         esi

		faddp       st(1),st

		fstp        dword ptr [eax+02ch]	//	localToParent(3,2)
	}
#else
	localToParent(3,0) =
		obb.localToParent(3,0)*matrix(0,0)
		 + obb.localToParent(3,1)*matrix(1,0)
		 + obb.localToParent(3,2)*matrix(2,0)
		 + matrix(3,0);
	localToParent(3,1) =
		obb.localToParent(3,0)*matrix(0,1)
		 + obb.localToParent(3,1)*matrix(1,1)
		 + obb.localToParent(3,2)*matrix(2,1)
		 + matrix(3,1);
	localToParent(3,2) =
		obb.localToParent(3,0)*matrix(0,2)
		 + obb.localToParent(3,1)*matrix(1,2)
		 + obb.localToParent(3,2)*matrix(2,2)
		 + matrix(3,2);
#endif

	sphereRadius = obb.sphereRadius;
	return *this;
}
#endif

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
OBB::SeparatingAxis
	OBB::FindSeparatingAxis(const OBB& box) const
{
	Check_Object(this);
	Check_Object(&box);
	Verify(axisExtents.x >= 0.0f);
	Verify(axisExtents.y >= 0.0f);
	Verify(axisExtents.z >= 0.0f);
	Verify(box.axisExtents.x >= 0.0f);
	Verify(box.axisExtents.y >= 0.0f);
	Verify(box.axisExtents.z >= 0.0f);

	//
	//------------------------------------------
	// Get the distance between the centerpoints
	//------------------------------------------
	//
	Check_Object(&localToParent);
	Check_Object(&box.localToParent);
	Point3D
		distance(
			localToParent(3,0) - box.localToParent(3,0),
			localToParent(3,1) - box.localToParent(3,1),
			localToParent(3,2) - box.localToParent(3,2)
		);

	//
	//-----------------------------------------------------------------------
	// First check L = Ax.  The next 6 tests are basically face tests to try
	// and find a separating plane.  Both boxes are currently in parent space
	//-----------------------------------------------------------------------
	//
	Scalar ab[3][3];
	ab[0][0] =
		Fabs(
			localToParent(0,0) * box.localToParent(0,0)
			 + localToParent(0,1) * box.localToParent(0,1)
			 + localToParent(0,2) * box.localToParent(0,2)
		);
	ab[0][1] =
		Fabs(
			localToParent(0,0) * box.localToParent(1,0)
			 + localToParent(0,1) * box.localToParent(1,1)
			 + localToParent(0,2) * box.localToParent(1,2)
		);
	ab[0][2] =
		Fabs(
			localToParent(0,0) * box.localToParent(2,0)
			 + localToParent(0,1) * box.localToParent(2,1)
			 + localToParent(0,2) * box.localToParent(2,2)
		);

	Scalar radius =
		axisExtents.x
		 + box.axisExtents.x * ab[0][0]
		 + box.axisExtents.y * ab[0][1]
		 + box.axisExtents.z * ab[0][2];
	Scalar projection =
		localToParent(0,0) * distance.x
		 + localToParent(0,1) * distance.y
		 + localToParent(0,2) * distance.z;
	if (projection > radius || projection < -radius)
	{
		return A0;
	}

	//
	//-------------
	// Check L = Ay
	//-------------
	//
	ab[1][0] =
		Fabs(
			localToParent(1,0) * box.localToParent(0,0)
			 + localToParent(1,1) * box.localToParent(0,1)
			 + localToParent(1,2) * box.localToParent(0,2)
		);
	ab[1][1] =
		Fabs(
			localToParent(1,0) * box.localToParent(1,0)
			 + localToParent(1,1) * box.localToParent(1,1)
			 + localToParent(1,2) * box.localToParent(1,2)
		);
	ab[1][2] =
		Fabs(
			localToParent(1,0) * box.localToParent(2,0)
			 + localToParent(1,1) * box.localToParent(2,1)
			 + localToParent(1,2) * box.localToParent(2,2)
		);

	radius =
		axisExtents.y
		 + box.axisExtents.x * ab[1][0]
		 + box.axisExtents.y * ab[1][1]
		 + box.axisExtents.z * ab[1][2];
	projection =
		localToParent(1,0) * distance.x
		 + localToParent(1,1) * distance.y
		 + localToParent(1,2) * distance.z;
	if (projection > radius || projection < -radius)
	{
		return A1;
	}

	//
	//-------------
	// Check L = Az
	//-------------
	//
	ab[2][0] =
		Fabs(
			localToParent(2,0) * box.localToParent(0,0)
			 + localToParent(2,1) * box.localToParent(0,1)
			 + localToParent(2,2) * box.localToParent(0,2)
		);
	ab[2][1] =
		Fabs(
			localToParent(2,0) * box.localToParent(1,0)
			 + localToParent(2,1) * box.localToParent(1,1)
			 + localToParent(2,2) * box.localToParent(1,2)
		);
	ab[2][2] =
		Fabs(
			localToParent(2,0) * box.localToParent(2,0)
			 + localToParent(2,1) * box.localToParent(2,1)
			 + localToParent(2,2) * box.localToParent(2,2)
		);

	radius =
		axisExtents.z
		 + box.axisExtents.x * ab[2][0]
		 + box.axisExtents.y * ab[2][1]
		 + box.axisExtents.z * ab[2][2];
	projection =
		localToParent(2,0) * distance.x
		 + localToParent(2,1) * distance.y
		 + localToParent(2,2) * distance.z;
	if (projection > radius || projection < -radius)
	{
		return A2;
	}

	//
	//-------------
	// Check L = Bx
	//-------------
	//
	radius =
		box.axisExtents.x 
		 + axisExtents.x * ab[0][0]
		 + axisExtents.y * ab[1][0]
		 + axisExtents.z * ab[2][0];
	projection =
		box.localToParent(0,0) * distance.x
		 + box.localToParent(0,1) * distance.y
		 + box.localToParent(0,2) * distance.z;
	if (projection > radius || projection < -radius)
	{
		return B0;
	}

	//
	//-------------
	// Check L = By
	//-------------
	//
	radius =
		box.axisExtents.y
		 + axisExtents.x * ab[0][1]
		 + axisExtents.y * ab[1][1]
		 + axisExtents.z * ab[2][1];
	projection =
		box.localToParent(1,0) * distance.x
		 + box.localToParent(1,1) * distance.y
		 + box.localToParent(1,2) * distance.z;
	if (projection > radius || projection < -radius)
	{
		return B1;
	}

	//
	//-------------
	// Check L = Bz
	//-------------
	//
	radius =
		box.axisExtents.z
		 + axisExtents.x * ab[0][2]
		 + axisExtents.y * ab[1][2]
		 + axisExtents.z * ab[2][2];
	projection =
		box.localToParent(2,0) * distance.x
		 + box.localToParent(2,1) * distance.y
		 + box.localToParent(2,2) * distance.z;
	if (projection > radius || projection < -radius)
	{
		return B2;
	}

	//
	//-------------------------------------------------------------------------
	// We have finished with the separation tests based upon the box faces.  We
	// now need to test with the edges, so make a matrix that converts local b
	// to local a, and figure a the new distance vector relative to a
	//-------------------------------------------------------------------------
	//
	LinearMatrix4D parent_to_a;
	parent_to_a.Invert(localToParent);
	LinearMatrix4D b_to_a;
	b_to_a.Multiply(box.localToParent, parent_to_a);
	distance = b_to_a;

	//
	//-------------------------------------------------------------------------
	// L = Ax crossed with Bx.  Since B has now been expressed in A, Ax can now
	// be simplified to <1,0,0>, making the cross-product much simpler
	//-------------------------------------------------------------------------
	//
	Scalar minor[3][3];
	minor[1][0] = Fabs(b_to_a(0,1)*b_to_a(2,2) - b_to_a(2,1)*b_to_a(0,2));
	minor[2][0] = Fabs(b_to_a(0,1)*b_to_a(1,2) - b_to_a(1,1)*b_to_a(0,2));

	Scalar ba[3][3];
	ba[0][1] = Fabs(b_to_a(0,1));
	ba[0][2] = Fabs(b_to_a(0,2));

	radius =
		axisExtents.y*ba[0][2]
		 + axisExtents.z*ba[0][1]
		 + box.axisExtents.y*minor[2][0]
		 + box.axisExtents.z*minor[1][0];
	projection = distance.z*b_to_a(0,1) - distance.y*b_to_a(0,2);
	if (projection > radius || projection < -radius)
	{
		return A0xB0;
	}

	//
	//-----------------------
	// L = Ax crossed with By
	//-----------------------
	//
	minor[0][0] = Fabs(b_to_a(1,1)*b_to_a(2,2) - b_to_a(2,1)*b_to_a(1,2));

	ba[1][1] = Fabs(b_to_a(1,1));
	ba[1][2] = Fabs(b_to_a(1,2));

	radius =
		axisExtents.y*ba[1][2]
		 + axisExtents.z*ba[1][1]
		 + box.axisExtents.x*minor[2][0]
		 + box.axisExtents.z*minor[0][0];
	projection = distance.z*b_to_a(1,1) - distance.y*b_to_a(1,2);
	if (projection > radius || projection < -radius)
	{
		return A0xB1;
	}

	//
	//-----------------------
	// L = Ax crossed with Bz
	//-----------------------
	//
	ba[2][1] = Fabs(b_to_a(2,1));
	ba[2][2] = Fabs(b_to_a(2,2));

	radius =
		axisExtents.y*ba[2][2]
		 + axisExtents.z*ba[2][1]
		 + box.axisExtents.x*minor[1][0]
		 + box.axisExtents.y*minor[0][0];
	projection = distance.z*b_to_a(2,1) - distance.y*b_to_a(2,2);
	if (projection > radius || projection < -radius)
	{
		return A0xB2;
	}

	//
	//-----------------------
	// L = Ay crossed with Bx
	//-----------------------
	//
	minor[2][1] = Fabs(b_to_a(0,2)*b_to_a(1,0) - b_to_a(0,0)*b_to_a(1,2));
	minor[1][1] = Fabs(b_to_a(0,0)*b_to_a(2,2) - b_to_a(2,0)*b_to_a(1,2));

	ba[0][0] = Fabs(b_to_a(0,0));

	radius =
		axisExtents.x*ba[0][2]
		 + axisExtents.z*ba[0][0]
		 + box.axisExtents.y*minor[2][1]
		 + box.axisExtents.z*minor[1][1];
	projection = distance.x*b_to_a(0,2) - distance.z*b_to_a(0,0);
	if (projection > radius || projection < -radius)
	{
		return A1xB0;
	}

	//
	//-----------------------
	// L = Ay crossed with By
	//-----------------------
	//
	minor[0][1] = Fabs(b_to_a(1,2)*b_to_a(2,0) - b_to_a(1,0)*b_to_a(2,2));

	ba[1][0] = Fabs(b_to_a(1,0));

	radius =
		axisExtents.x*ba[1][2]
		 + axisExtents.z*ba[1][0]
		 + box.axisExtents.x*minor[2][1]
		 + box.axisExtents.z*minor[0][1];
	projection = distance.x*b_to_a(1,2) - distance.z*b_to_a(1,0);
	if (projection > radius || projection < -radius)
	{
		return A1xB1;
	}

	//
	//-----------------------
	// L = Ay crossed with Bz
	//-----------------------
	//
	ba[2][0] = Fabs(b_to_a(2,0));

	radius =
		axisExtents.x*ba[2][2]
		 + axisExtents.z*ba[2][0]
		 + box.axisExtents.x*minor[1][1]
		 + box.axisExtents.y*minor[0][1];
	projection = distance.x*b_to_a(2,2) - distance.z*b_to_a(2,0);
	if (projection > radius || projection < -radius)
	{
		return A1xB2;
	}

	//
	//-----------------------
	// L = Az crossed with Bx
	//-----------------------
	//
	minor[2][2] = Fabs(b_to_a(1,1)*b_to_a(0,0) - b_to_a(0,1)*b_to_a(1,0));
	minor[1][2] = Fabs(b_to_a(2,1)*b_to_a(0,0) - b_to_a(2,0)*b_to_a(0,1));

	radius =
		axisExtents.x*ba[0][1]
		 + axisExtents.y*ba[0][0]
		 + box.axisExtents.y*minor[2][2]
		 + box.axisExtents.z*minor[1][2];
	projection = distance.y*b_to_a(0,0) - distance.x*b_to_a(0,1);
	if (projection > radius || projection < -radius)
	{
		return A2xB0;
	}

	//
	//-----------------------
	// L = Az crossed with By
	//-----------------------
	//
	minor[0][2] = Fabs(b_to_a(1,0)*b_to_a(2,1) - b_to_a(2,0)*b_to_a(1,1));

	radius =
		axisExtents.x*ba[1][1]
		 + axisExtents.y*ba[1][0]
		 + box.axisExtents.x*minor[2][2]
		 + box.axisExtents.z*minor[0][2];
	projection = distance.y*b_to_a(1,0) - distance.x*b_to_a(1,1);
	if (projection > radius || projection < -radius)
	{
		return A2xB1;
	}

	//
	//-----------------------
	// L = Az crossed with Bz
	//-----------------------
	//
	radius =
		axisExtents.x*ba[2][1]
		 + axisExtents.y*ba[2][0]
		 + box.axisExtents.x*minor[1][2]
		 + box.axisExtents.y*minor[0][2];
	projection = distance.y*b_to_a(2,0) - distance.x*b_to_a(2,1);
	if (projection > radius || projection < -radius)
	{
		return A2xB2;
	}

	return NoSeparation;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	OBB::Union(
		const OBB &first,
		const OBB &second
	)
{
	Check_Pointer(this);
	Check_Object(&first);
	Check_Object(&second);

	//
	//-------------------------------------------------
	// Learn about the spheres surrounding the two OBBs
	//-------------------------------------------------
	//
	Point3D c1(first.localToParent);
	Point3D c2(second.localToParent);
	Vector3D diff;
	diff.Subtract(c2, c1);
	Scalar len = diff.GetLength();

	//
	//---------------------------------------------------
	// See if the first sphere is contained in the second
	//---------------------------------------------------
	//
	if (len+first.sphereRadius <= second.sphereRadius || !first.sphereRadius)
	{
		if (this != &second)
			*this = second;
		return;
	}

	//
	//---------------------------------------------------
	// See if the second sphere is contained in the first
	//---------------------------------------------------
	//
	if (len+second.sphereRadius <= first.sphereRadius || !second.sphereRadius)
	{
		if (this != &first)
			*this = first;
		return;
	}

	//
	//-------------------------------------------------------
	// The new sphere will lie somewhere between the old ones
	//-------------------------------------------------------
	//
	localToParent = LinearMatrix4D::Identity;
	axisExtents = Vector3D::Identity;
	c1.Lerp(c1, c2, (len + second.sphereRadius - first.sphereRadius) / (2.0f*len));
	localToParent.BuildTranslation(c1);
	sphereRadius = 0.5f * (len + first.sphereRadius + second.sphereRadius);
}
